/*
 * Copyright 2013 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.aplink.generic.google.maps.ui;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.aplink.sg.R;

/**
 * IconGenerator generates icons that contain text (or custom content) within an
 * info window-like shape.
 * <p/>
 * The icon {@link Bitmap}s generated by the factory should be used in
 * conjunction with a
 * {@link com.google.android.gms.maps.model.BitmapDescriptorFactory}.
 * <p/>
 * This class is not thread safe.
 */
public class IconGenerator {
    public static final int STYLE_BLUE = 4;

    public static final int STYLE_DEFAULT = 1;
    public static final int STYLE_GREEN = 5;
    public static final int STYLE_ORANGE = 7;
    public static final int STYLE_PURPLE = 6;

    public static final int STYLE_RED = 3;

    public static final int STYLE_WHITE = 2;

    private static int getBackground(final int style) {
        switch (style) {
        default:
        case STYLE_DEFAULT:
        case STYLE_WHITE:
            return R.drawable.bubble_white;
        case STYLE_RED:
            return R.drawable.bubble_red;
        case STYLE_BLUE:
            return R.drawable.bubble_blue;
        case STYLE_GREEN:
            return R.drawable.bubble_green;
        case STYLE_PURPLE:
            return R.drawable.bubble_purple;
        case STYLE_ORANGE:
            return R.drawable.bubble_orange;
        }
    }

    private static int getTextStyle(final int style) {
        switch (style) {
        default:
        case STYLE_DEFAULT:
        case STYLE_WHITE:
            return R.style.Bubble_TextAppearance_Dark;
        case STYLE_RED:
        case STYLE_BLUE:
        case STYLE_GREEN:
        case STYLE_PURPLE:
        case STYLE_ORANGE:
            return R.style.Bubble_TextAppearance_Light;
        }
    }

    private final float mAnchorU = 0.5f;

    private final float mAnchorV = 1f;

    private ViewGroup mContainer;

    private View mContentView;

    private final Context mContext;

    private int mRotation;

    private RotationLayout mRotationLayout;

    private TextView mTextView;

    /**
     * Creates a new IconGenerator with the default style.
     */
    public IconGenerator(final Context context) {
        mContext = context;
    }

    /**
     * Ensure views are ready. This allows us to lazily inflate the main layout.
     */
    private void ensureViewsSetUp() {
        if (mContainer == null) {
            mContainer = (ViewGroup) LayoutInflater.from(mContext).inflate(
                    R.layout.google__text_bubble, null);
            mRotationLayout = (RotationLayout) mContainer.getChildAt(0);
            mContentView = mTextView = (TextView) mRotationLayout
                    .findViewById(R.id.text);
        }
    }

    /**
     * @return u coordinate of the anchor, with rotation applied.
     */
    public float getAnchorU() {
        return rotateAnchor(mAnchorU, mAnchorV);
    }

    /**
     * @return v coordinate of the anchor, with rotation applied.
     */
    public float getAnchorV() {
        return rotateAnchor(mAnchorV, mAnchorU);
    }

    /**
     * Not thread safe.
     */
    private ViewGroup getContainer() {
        ensureViewsSetUp();
        return mContainer;
    }

    /**
     * Creates an icon with the current content and style.
     * <p/>
     * This method is useful if a custom view has previously been set, or if
     * text content is not applicable.
     */
    public Bitmap makeIcon() {
        final ViewGroup container = getContainer();

        final int measureSpec = View.MeasureSpec.makeMeasureSpec(0,
                View.MeasureSpec.UNSPECIFIED);
        container.measure(measureSpec, measureSpec);

        int measuredWidth = container.getMeasuredWidth();
        int measuredHeight = container.getMeasuredHeight();

        container.layout(0, 0, measuredWidth, measuredHeight);

        if ((mRotation == 1) || (mRotation == 3)) {
            measuredHeight = container.getMeasuredWidth();
            measuredWidth = container.getMeasuredHeight();
        }

        final Bitmap r = Bitmap.createBitmap(measuredWidth, measuredHeight,
                Bitmap.Config.ARGB_8888);
        r.eraseColor(Color.TRANSPARENT);

        final Canvas canvas = new Canvas(r);

        if (mRotation == 0) {
            // do nothing
        } else if (mRotation == 1) {
            canvas.translate(measuredWidth, 0);
            canvas.rotate(90);
        } else if (mRotation == 2) {
            canvas.rotate(180, measuredWidth / 2, measuredHeight / 2);
        } else {
            canvas.translate(0, measuredHeight);
            canvas.rotate(270);
        }
        container.draw(canvas);
        return r;
    }

    /**
     * Sets the text content, then creates an icon with the current style.
     * 
     * @param text
     *            the text content to display inside the icon.
     */
    public Bitmap makeIcon(final String text) {
        ensureViewsSetUp();

        if (mTextView != null) {
            mTextView.setText(text);
        }

        return makeIcon();
    }

    /**
     * Rotates the anchor around (u, v) = (0, 0).
     */
    private float rotateAnchor(final float u, final float v) {
        switch (mRotation) {
        case 0:
            return u;
        case 1:
            return 1 - v;
        case 2:
            return 1 - u;
        case 3:
            return v;
        }
        throw new IllegalStateException();
    }

    /**
     * Set the background to a given Drawable, or remove the background.
     * 
     * @param background
     *            the Drawable to use as the background, or null to remove the
     *            background.
     */
    @SuppressWarnings("deprecation")
    // View#setBackgroundDrawable is compatible with pre-API level 16 (Jelly
    // Bean).
    public void setBackground(final Drawable background) {
        getContainer().setBackgroundDrawable(background);

        // Force setting of padding.
        // setBackgroundDrawable does not call setPadding if the background has
        // 0 padding.
        if (background != null) {
            final Rect rect = new Rect();
            background.getPadding(rect);
            getContainer().setPadding(rect.left, rect.top, rect.right,
                    rect.bottom);
        } else {
            getContainer().setPadding(0, 0, 0, 0);
        }
    }

    /**
     * Sets the padding of the content view. The default padding of the content
     * view (i.e. text view) is 5dp top/bottom and 10dp left/right.
     * 
     * @param left
     *            the left padding in pixels.
     * @param top
     *            the top padding in pixels.
     * @param right
     *            the right padding in pixels.
     * @param bottom
     *            the bottom padding in pixels.
     */
    public void setContentPadding(final int left, final int top,
            final int right, final int bottom) {
        ensureViewsSetUp();
        mContentView.setPadding(left, top, right, bottom);
    }

    /**
     * Rotates the contents of the icon.
     * 
     * @param degrees
     *            the amount the contents should be rotated, as a multiple of 90
     *            degrees.
     */
    public void setContentRotation(final int degrees) {
        ensureViewsSetUp();
        mRotationLayout.setViewRotation(degrees);
    }

    /**
     * Sets the child view for the icon.
     * <p/>
     * If the view contains a {@link TextView} with the id "text", operations
     * such as {@link #setTextAppearance} and {@link #makeIcon(String)} will
     * operate upon that {@link TextView}.
     */
    public void setContentView(final View contentView) {
        ensureViewsSetUp();
        mRotationLayout.removeAllViews();
        mRotationLayout.addView(contentView);
        mContentView = contentView;
        final View view = mRotationLayout.findViewById(R.id.text);
        mTextView = view instanceof TextView ? (TextView) view : null;
    }

    /**
     * Rotates the icon.
     * 
     * @param degrees
     *            the amount the icon should be rotated, as a multiple of 90
     *            degrees.
     */
    public void setRotation(final int degrees) {
        mRotation = ((degrees + 360) % 360) / 90;
    }

    /**
     * Sets the style of the icon. The style consists of a background and text
     * appearance.
     */
    public void setStyle(final int style) {
        setBackground(mContext.getResources().getDrawable(
                IconGenerator.getBackground(style)));
        setTextAppearance(mContext, IconGenerator.getTextStyle(style));
    }

    /**
     * Sets the text color, size, style, hint color, and highlight color from
     * the specified <code>TextAppearance</code> resource.
     * 
     * @param resid
     *            the identifier of the resource.
     */
    public void setTextAppearance(final Context context, final int resid) {
        ensureViewsSetUp();
        if (mTextView != null) {
            mTextView.setTextAppearance(context, resid);
        }
    }

    /**
     * Sets the text color, size, style, hint color, and highlight color from
     * the specified <code>TextAppearance</code> resource.
     * 
     * @param resid
     *            the identifier of the resource.
     */
    public void setTextAppearance(final int resid) {
        setTextAppearance(mContext, resid);
    }
}
